package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {
    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;
    @Autowired
    private UserInfoFeignClient userInfoFeignClient;
    @Autowired
    private CategoryFeignClient categoryFeignClient;
    @Autowired
    private AlbumIndexRepository albumIndexRepository;
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private SuggestIndexRepository suggestIndexRepository;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 上架专辑
     * @param albumId
     */
    @Override
    public void upperAlbum(Long albumId) {
        //创建索引库
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //  远程调用专辑微服务获取专辑对象
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
            //  判断回调对象是否为空
            Assert.notNull(albumInfoResult, "专辑结果集不存在");
            AlbumInfo albumInfo = albumInfoResult.getData();
            Assert.notNull(albumInfo, "专辑对象不存在");
            //  直接属性拷贝
            BeanUtils.copyProperties(albumInfo, albumInfoIndex);
            return albumInfo;
        }, threadPoolExecutor);


        CompletableFuture<Void> userInfoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //远程调用用户信息 获取用户名
            Result<UserInfoVo> userInfoResult = userInfoFeignClient.getUserInfoVo(albumInfo.getUserId());
            //判断是否为空
            Assert.notNull(userInfoResult, "用户信息不存在");
            //获取数据
            UserInfoVo userInfoVo = userInfoResult.getData();
            //判断
            Assert.notNull(userInfoVo, "用户对象不存在");
            //赋值
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolExecutor);


        CompletableFuture<Void> categoryCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //远程调用 获取分类数据 根据专辑信息里面的三级分类id
            Result<BaseCategoryView> baseCategoryViewResult = categoryFeignClient.getCategoryView(albumInfo.getCategory3Id());
            //判断是否为空
            Assert.notNull(baseCategoryViewResult, "分类信息不存在");
            //获取数据
            BaseCategoryView baseCategoryView = baseCategoryViewResult.getData();
            //判断
            Assert.notNull(baseCategoryView, "分类对象不存在");
            //赋值
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
        }, threadPoolExecutor);


        CompletableFuture<Void> statCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //远程调用获取专辑统计信息
            Result<AlbumStatVo> albumListVoResult = albumInfoFeignClient.getAlbumStatVo(albumInfo.getId());
            //判断是否为空
            Assert.notNull(albumListVoResult, "专辑统计信息不存在");
            //获取数据
            AlbumStatVo albumListVo = albumListVoResult.getData();
            //判断
            Assert.notNull(albumListVo, "专辑统计对象不存在");
            //赋值
            albumInfoIndex.setPlayStatNum(albumListVo.getPlayStatNum());
            albumInfoIndex.setSubscribeStatNum(albumListVo.getSubscribeStatNum());
            albumInfoIndex.setBuyStatNum(albumListVo.getBuyStatNum());
            albumInfoIndex.setCommentStatNum(albumListVo.getCommentStatNum());
        }, threadPoolExecutor);


        //热度
        albumInfoIndex.setHotScore((double)new Random().nextInt(1000));


        CompletableFuture<Void> valueCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //获取属性值
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (!CollectionUtils.isEmpty(albumAttributeValueVoList)) {
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList.stream().map(albumAttributeValue -> {
                    //创建对象
                    AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                    //拷贝
                    BeanUtils.copyProperties(albumAttributeValue, attributeValueIndex);
                    return attributeValueIndex;
                }).collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
        }, threadPoolExecutor);

        //数据汇总
        CompletableFuture.allOf(
                albumInfoCompletableFuture,
                userInfoCompletableFuture,
                categoryCompletableFuture,
                statCompletableFuture,
                valueCompletableFuture
        ).join();
        //将数据保存到es中
        albumIndexRepository.save(albumInfoIndex);

        //初始化索引库数据
        //标题
        SuggestIndex suggestIndexTitle = new SuggestIndex();
        suggestIndexTitle.setId(UUID.randomUUID().toString().replaceAll("-",""));
        suggestIndexTitle.setTitle(albumInfoIndex.getAlbumTitle());
        suggestIndexTitle.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumTitle()}));
        suggestIndexTitle.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumTitle())}));
        suggestIndexTitle.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumTitle())}));
        suggestIndexRepository.save(suggestIndexTitle);
        //专辑简介
        SuggestIndex suggestIndexInfo = new SuggestIndex();
        suggestIndexInfo.setId(UUID.randomUUID().toString().replaceAll("-",""));
        suggestIndexInfo.setTitle(albumInfoIndex.getAlbumIntro());
        suggestIndexInfo.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumIntro()}));
        suggestIndexInfo.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumIntro())}));
        suggestIndexInfo.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumIntro())}));
        suggestIndexRepository.save(suggestIndexInfo);
        //作者
        SuggestIndex suggestIndexAnnouncerName = new SuggestIndex();
        suggestIndexAnnouncerName.setId(UUID.randomUUID().toString().replaceAll("-",""));
        suggestIndexAnnouncerName.setTitle(albumInfoIndex.getAnnouncerName());
        suggestIndexAnnouncerName.setKeyword(new Completion(new String[]{albumInfoIndex.getAnnouncerName()}));
        suggestIndexAnnouncerName.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAnnouncerName())}));
        suggestIndexAnnouncerName.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAnnouncerName())}));
        suggestIndexRepository.save(suggestIndexAnnouncerName);
    }

    /**
     * 下架专辑
     * @param albumId
     */
    @Override
    public void lowerAlbum(Long albumId) {
        albumIndexRepository.deleteById(albumId);
    }

    /**
     * 全文检索
     * @param albumInfoQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        //生成动态dsl语句
        SearchRequest searchRequest=queryBuildDsl(albumIndexQuery);
        //查询数据
        SearchResponse<AlbumInfoIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //获取结果并赋值
        AlbumSearchResponseVo albumSearchResponseVo = parseResultData(searchResponse);
        //总记录数
        albumSearchResponseVo.setTotal(searchResponse.hits().total().value());
        //每页显示内容
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        //当前页
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        //总页数
        Long totalPages = (albumSearchResponseVo.getTotal()+albumSearchResponseVo.getPageSize()-1)/ albumSearchResponseVo.getPageSize();
        albumSearchResponseVo.setTotalPages(totalPages);
        return albumSearchResponseVo;
    }

    /**
     * 赋值
     * @param searchResponse
     * @return
     */
    private AlbumSearchResponseVo parseResultData(SearchResponse<AlbumInfoIndex> searchResponse) {
        //创建对象 AlbumSearchResponseVo
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        //创建集合
        List<AlbumInfoIndexVo> list=new ArrayList<>();
        //遍历数据
        for (Hit<AlbumInfoIndex> hit : searchResponse.hits().hits()) {
            //创建对象
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            //获取索引库数据
            AlbumInfoIndex albumInfoIndex = hit.source();
            //赋值
            BeanUtils.copyProperties(albumInfoIndex,albumInfoIndexVo);
            //判断是否有高亮数据
            if (null!=hit.highlight().get("albumTitle")){
                //获取高亮数据
                String albumTitle = hit.highlight().get("albumTitle").get(0);
                albumInfoIndexVo.setAlbumTitle(albumTitle);
            }
            //添加到集合中
            list.add(albumInfoIndexVo);
        }
        //添加数据
        albumSearchResponseVo.setList(list);
        return albumSearchResponseVo;
    }

    /**
     * 生成动态dsl语句
     * @param albumIndexQuery
     * @return
     */
    private SearchRequest queryBuildDsl(AlbumIndexQuery albumIndexQuery) {
        //创建对象 SearchRequest
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        searchRequestBuilder.index("albuminfo");
        //创建对象 BoolQuery
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();


        //获取关键字 判断是否是关键字搜索
        String keyword = albumIndexQuery.getKeyword();
        if (!StringUtils.isEmpty(keyword)){
            boolQueryBuilder.should(s->s.match(m->m.field("albumTitle").query(keyword)))
                    .should(s->s.match(m->m.field("albumIntro").query(keyword)))
                    .should(s->s.match(m->m.field("announcerName").query(keyword)));
            //高亮
            searchRequestBuilder.highlight(h->h.fields("albumTitle",h1->h1.preTags("<span style=color:red>").postTags("</span>")));
        }


        //判断是否通过分类id获取
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory1Id())){
            boolQueryBuilder.filter(f->f.term(t->t.field("category1Id").value(albumIndexQuery.getCategory1Id())));
        }
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory2Id())){
            boolQueryBuilder.filter(f->f.term(t->t.field("category2Id").value(albumIndexQuery.getCategory2Id())));
        }
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory3Id())){
            boolQueryBuilder.filter(f->f.term(t->t.field("category3Id").value(albumIndexQuery.getCategory3Id())));
        }


        //根据属性与属性值id进行过滤
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)){
            for (String s : attributeList) {
                //分割 属性id:属性值id
                String[] split = s.split(":");
                //判断
                if (null!=split && split.length==2){
                    //获取属性id
                    String attrId = split[0];
                    //获取属性值
                    String attrValueId = split[1];
                    boolQueryBuilder.filter(f->f.nested(n->n.path("attributeValueIndexList")
                            .query(q->q.bool(b->b.filter(f1->f1.term(t->t.field("attributeValueIndexList.attributeId").value(attrId)))
                                    .filter(f2->f2.term(t2->t2.field("attributeValueIndexList.valueId").value(attrValueId)))))));
                }
            }
        }


        //设置排序 排序（综合排序[1:desc] 播放量[2:desc] 发布时间[3:desc]；asc:升序 desc:降序）
        String order = albumIndexQuery.getOrder();
        //判断
        if (!StringUtils.isEmpty(order)){
            //分割 1:desc
            String[] split = order.split(":");
            //判断
            if (null!=split && split.length==2){
                //声明一个字段
                String field = "";
                switch (split[0]){
                    case "1":
                        field="hotScore";
                        break;
                    case "2":
                        field="playStatNum";
                        break;
                    case "3":
                        field="createTime";
                        break;
                }
                //重新声明变量
                String finalField = field;
                searchRequestBuilder.sort(s->s.field(f->f.field(finalField).order("asc".equals(split[1])? SortOrder.Asc:SortOrder.Desc)));
            }
        }


        //分页
        int from = (albumIndexQuery.getPageNo()-1)*albumIndexQuery.getPageSize();
        searchRequestBuilder.from(from);
        searchRequestBuilder.size(albumIndexQuery.getPageSize());
        //生成最终查询对象
        BoolQuery build = boolQueryBuilder.build();
        //调用query
        searchRequestBuilder.query(build._toQuery());
        //方法创建对象
        SearchRequest searchRequest = searchRequestBuilder.build();
        System.out.println("dsl:\t"+searchRequest.toString());
        return searchRequest;
    }

    /**
     * 查询频道页数据
     * @param category1Id
     * @return
     */
    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        //远程调用获取三级分类数据
        Result<List<BaseCategory3>> baseCategory3 = categoryFeignClient.findTopBaseCategory3(category1Id);
        //判断
        Assert.notNull(baseCategory3, "获取三级分类集合失败");
        List<BaseCategory3> category3Data = baseCategory3.getData();
        Assert.notNull(category3Data, "获取三级分类对象失败");
        //将List 转为Map
        Map<Long, BaseCategory3> category3Map = category3Data.stream().collect(Collectors.toMap(category3 -> category3.getId(), category3 -> category3));
        List<FieldValue> fieldValues = category3Data.stream().map(category3 -> FieldValue.of(category3.getId())).collect(Collectors.toList());
        //编写dsl
        SearchResponse<AlbumInfoIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(s -> s.index("albuminfo")
                    .query(q -> q.terms(t -> t.field("category3Id").terms(ts -> ts.value(fieldValues))))
                    .aggregations("agg_category3Id", a -> a.terms(t -> t.field("category3Id"))
                            .aggregations("agg_top6", a1 -> a1.topHits(t1 -> t1.size(6)
                                    .sort(s1 -> s1.field(f1 -> f1.field("hotScore").order(SortOrder.Desc)))))), AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //获取数据
        Aggregate aggCategory3Id = searchResponse.aggregations().get("agg_category3Id");
        List<Map<String, Object>> mapList = aggCategory3Id.lterms().buckets().array().stream().map(bucket -> {
            //创建一个map对象
            Map<String, Object> map = new HashMap<>();
            //获取三级分类id
            long category3Id = bucket.key();
            //获取置顶数据
            Aggregate aggTop6 = bucket.aggregations().get("agg_top6");
            List<AlbumInfoIndex> infoIndexList = aggTop6.topHits().hits().hits().stream().map(hit -> {
                //获取数据
                JsonData source = hit.source();
                AlbumInfoIndex albumInfoIndex = JSON.parseObject(source.toString(), AlbumInfoIndex.class);
                return albumInfoIndex;
            }).collect(Collectors.toList());
            map.put("baseCategory3", category3Map.get(category3Id));
            map.put("list", infoIndexList);
            return map;
        }).collect(Collectors.toList());
        return mapList;
    }

    /**
     * 关键字自动补全
     * @param keyword
     * @return
     */
    @Override
    public List<String> completeSuggest(String keyword) {
        //编写dsl语句
        SearchResponse<SuggestIndex> search = null;
        try {
            search = elasticsearchClient.search(s -> s.index("suggestinfo")
                            .suggest(s1 -> s1.suggesters("suggestionKeyword",
                                            s2 -> s2.prefix(keyword).completion(c -> c.field("keyword").skipDuplicates(true).fuzzy(f -> f.fuzziness("auto"))))
                                    .suggesters("suggestionkeywordPinyin",
                                            s3 -> s3.prefix(keyword).completion(c1 -> c1.field("keywordPinyin").skipDuplicates(true).fuzzy(f1 -> f1.fuzziness("auto"))))
                                    .suggesters("suggestionkeywordSequence",
                                            s4 -> s4.prefix(keyword).completion(c2 -> c2.field("keywordSequence").skipDuplicates(true).fuzzy(f2 -> f2.fuzziness("auto")))))
                    , SuggestIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //获取数据
        //创建集合
        List<String> list = new ArrayList<>();
        list.addAll(this.getSuggestResultData(search,"suggestionKeyword"));
        list.addAll(this.getSuggestResultData(search,"suggestionkeywordPinyin"));
        list.addAll(this.getSuggestResultData(search,"suggestionkeywordSequence"));
        return list;
    }

    private Collection<String> getSuggestResultData(SearchResponse<SuggestIndex> search, String keyword) {
        //获取数据 suggest
        List<Suggestion<SuggestIndex>> suggestions = search.suggest().get(keyword);
        if (!CollectionUtils.isEmpty(suggestions)){
            for (Suggestion<SuggestIndex> suggestion : suggestions) {
                List<String> collect = suggestion.completion().options().stream().map(option -> {
                    return option.source().getTitle();
                }).collect(Collectors.toList());
                return collect;
            }
        }
        return null;
    }

    /**
     * 更新最近专辑排行
     */
    @Override
    public void updateLatelyAlbumRanking() {
        //通过远程调用获取一级分类数据
        Result<List<BaseCategory1>> allCategory1Result = categoryFeignClient.findAllCategory1();
        Assert.notNull(allCategory1Result, "获取一级分类数据失败");
        List<BaseCategory1> category1List = allCategory1Result.getData();
        Assert.notNull(category1List, "获取一级分类数据失败");
        for (BaseCategory1 baseCategory1 : category1List) {
            //定义5个维度
            String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
            for (String rank : rankingDimensionArray) {
                //编写dsl语句
                SearchResponse<AlbumInfoIndex> search = null;
                try {
                    search = elasticsearchClient.search(s -> s.index("albuminfo")
                                    .query(q -> q.term(t -> t.field("category1Id").value(baseCategory1.getId())))
                                    .sort(s1 -> s1.field(f -> f.field(rank).order(SortOrder.Desc)))
                            , AlbumInfoIndex.class);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                //将数据存储到redis hset key=category1Id field=rank value=SearchResponse<AlbumInfoIndex>
                //获取数据
                List<AlbumInfoIndex> infoIndexList = search.hits().hits().stream().map(Hit::source).collect(Collectors.toList());
                redisTemplate.opsForHash().put(RedisConstant.RANKING_KEY_PREFIX+baseCategory1.getId(),rank,infoIndexList);
            }
        }
    }

    /**
     * 获取排行榜
     * @param category1Id
     * @param dimension
     * @return
     */
    @Override
    public List<AlbumInfoIndexVo> findRankingList(Long category1Id, String dimension) {
        //hset key field
        List<AlbumInfoIndexVo> albumInfoIndexVoList = (List<AlbumInfoIndexVo>) redisTemplate.opsForHash().get(RedisConstant.RANKING_KEY_PREFIX + category1Id, dimension);
        return albumInfoIndexVoList;
    }
}
